﻿param(
    $Server = "",
    $srGUID = "",
    $debug = 0
)

if (!(Get-Module smlets)) {Import-Module smlets -force -ErrorAction stop}

#Functions
#Walks through the activity tree in order to find the manual activities that are being used as decision activities
function WalkActivityTree {
    param($baseObj)
    #START DEBUG
    if($debug -eq 1){ 
		Write-Host -ForegroundColor Yellow "WalkActivityTree:" $baseObj.Name 
		"ID: " + $baseObj.Get_Id() 
	}
    #END DEBUG

    #Returns all of the activities from the base object
    $activities = GetAllActivities($baseObj) 

    foreach($activity in $activities) {
        #If it's a PA, we walk into it to find it's children (recursive)
        if($activity.ClassName -eq $paClassName) { 
            WalkActivityTree $activity
        #Same behaviour as above
        } elseif($activity.ClassName -eq $saClassName) { 
            WalkActivityTree $activity
        #If the activity is a MA, we look for a valid decision
        } elseif (($activity.ClassName -eq $maClassName) `
            -and (($activity.ActivityDecision.Name -eq $enumYes) `
            -or ($activity.ActivityDecision.Name -eq $enumNo))) 
        {
            #START DEBUG
            if($debug -eq 1){
                "Parent        : " + (GetParentWI($activity)).DisplayName
                "Child         : " + $activity.Name
                "Child ID      : " + $activity.Get_Id()
                "Child Status  : " + $activity.Status.DisplayName
                "Child Stage   : " + $activity.Stage.DisplayName
                "Child Decision: " + $activity.ActivityDecision.DisplayName
            }
            #END DEBUG
            
			#If the activity was a decision we evaluate the decision value
            EvaluateMA $activity 

		#If that activity is a decision, but is set to undecided we ignore it
        } elseif (($activity.ClassName -eq $maClassName) `
            -and ($activity.ActivityDecision.Name -eq $enumUndecided))
        {
            if($debug -eq 1)
            {
                #START DEBUG
                if($debug -eq 1){
                    "Parent        : " + (GetParentWI($activity)).DisplayName
                    "Child         : " + $activity.Name
                    "Child ID      : " + $activity.Get_Id()
                    "Child Status  : " + $activity.Status.DisplayName
                    "Child Stage   : " + $activity.Stage.DisplayName
                    "Child Decision: " + $activity.ActivityDecision.DisplayName
                }
                #END DEBUG
            }
        }
    }
}

#Evaluate the manual activity that is sent to it, this will make sure it's a valid decision and that it's 'in progress'
#it will then determine that correct template and apply it
function EvaluateMA {
    param($baseObj)
    
    #START DEBUG
    if($debug -eq 1){ Write-Host -ForegroundColor Yellow "EvaluateMA:"$baseObj.Name }
    #END DEBUG

    if ( (  ($baseObj.ActivityDecision.Name -eq $enumYes) `
        -or ($baseObj.ActivityDecision.Name -eq $enumNo) )`
        -and ($baseObj.Status.Name -eq $enumInProgress))
    {
        $parentObj = GetParentWI($baseObj) #retreives the parent workitem of the decision activity (so we can build the template ID)
        $seqArray = GetSequenceArray(GetAllActivities($parentObj)) #this takes a snapshot of the current sequence of activities that already exist
        
        #START DEBUG
        if($debug -eq 1){ Write-Host -ForegroundColor Yellow "EvaluateMA:ParentWI:"$baseObj }
        #END DEBUG

		#Here we build the template ID by compiling the template IDs of each piece of the puzzle
        $templateName = "{DecisionEngine}" + `
            "{" + $rootObj.DecisionTemplateId + "}" + `
            "{" + $parentObj.DecisionTemplateId + "}" +
            "{" + $baseObj.DecisionTemplateId + "}" + `
            "{" + $baseObj.ActivityDecision.DisplayName + "}$"
        #START DEBUG
        if($debug -eq 1){ Write-Host -ForegroundColor Yellow "EvaluateMA:TemplateName:"$templateName }
        #END DEBUG
		
		#Retrieves the template from service manager
        $template = Get-SCSMObjectTemplate -DisplayName $templateName -ComputerName $Server 
        #START DEBUG
        if($debug -eq 1){ Write-Host -ForegroundColor Yellow "EvaluateMA:TemplateFound:"$template }
        #END DEBUG
		
        #If the template is valid we apply it to the object (the parent SA of the decision activity)
        if($template) 
        {
			#Apply the template to the SA and pass the proper sequence of the activities, including the current sequence
            ApplyTemplateWithActivities -Id $parentObj.get_id() -TemplateId $template.get_id() -SequenceStart $seqArray.Count
        
            #This is required to kick off the activity status change workflow after activity completion
            UpdateActivitiesStatus $parentObj $seqArray.Count
			
			#Setup MA parameters
            $propHash = @{
                Status = $enumCompleted;
                Description = "$($activity.Description)`nCompleted by Cireson Decision Engine"
            }
			#Completes the MA that was used as a decision activity
            $activity | Set-SCSMObject -PropertyHashtable $propHash -ComputerName $Server
        }
        elseif($debug -eq 1)
        {
            Write-Host -ForegroundColor Red "No template was found."
        }

    }  
    elseif($debug -eq 1) 
    {
        Write-Host "There was nothing to do with $($baseObj.Name)"
    }
}

#Update the status of all new activities
function UpdateActivitiesStatus {
    param($parentObj,$startSeq = 0)

    #START DEBUG
    if($debug -eq 1){ Write-Host -ForegroundColor Yellow "UpdateActivitiesStatus:"$parentObj }
    #END DEBUG

	#Get all the child activities of the parent object
    $allActivities = GetAllActivities $parentObj
	
	#Walk through the activities and check their sequence (to make sure it's a newly added activity), then set their status to pending
    foreach($activity in $allActivities)
    {
        if($activity.SequenceId -ge $startSeq)
        {
            #START DEBUG
            if($debug -eq 1){ Write-Host -ForegroundColor Yellow "UpdatingNewActivity:"$activity }
            #END DEBUG

            $activity | Set-SCSMObject -Property Status -Value $enumPending -ComputerName $Server
        }
    }
}

#Walks UP a workitem's relationships in order to find the parent workitem and returns it
function GetParentWI {
    param($childObj)
    
    #START DEBUG
    if($debug -eq 1){ Write-Host -ForegroundColor Yellow "GetParentWI" }
    #END DEBUG

    $relObj = Get-SCSMRelationshipObject -ByTarget $childObj -ComputerName $Server 

    foreach($obj in $relObj) {
        if($obj.RelationshipID -eq '2da498be-0485-b2b2-d520-6ebd1698e61b') {
            $parentObj = Get-SCSMObject -Id ($obj.SourceObject.Id) -ComputerName $Server 
        }
    }
    return $parentObj
}

#This is a helper function that gets all of the child activities of an object or array of objects
function GetAllActivities {
    param($baseObj)
    #START DEBUG
    if($debug -eq 1){ 
		Write-Host -ForegroundColor Yellow "GetAllActivities:" $baseObj.Name 
		"ID: " + $baseObj.Get_Id()
	}
    #END DEBUG
    
    foreach($obj in $baseObj) {
        $act += Get-SCSMRelatedObject -SMObject $obj -ComputerName $Server 
    }

    return $act | sort sequenceid
}

function GetSequenceArray {
    param($activities)
    
    #START DEBUG
    if($debug -eq 1){ Write-Host -ForegroundColor Yellow "GetSequenceArray" }
    #END DEBUG

    $array = @()

    foreach($activity in $activities)
    {
        $array += ($activity.Name)
    }
    
    #START DEBUG
    if($debug -eq 1){ Write-Host -ForegroundColor Green "ReturnedArray:"$array }
    #END DEBUG

    return $array
}

#Original idea and framework code by Morten Meisler
#Applies a selected template to an existing workitem, this template can contain activities
function ApplyTemplateWithActivities {
    param (
        [guid]$Id,
        [string]$TemplateDisplayName,
        [guid]$TemplateId,
        [int]$SequenceStart
    )
    
	#START DEBUG
    if($debug -eq 1){ Write-Host -ForegroundColor Yellow "ApplyTemplateWithActivities" }
    #END DEBUG

    #Function to get activity id prefix from the activity settings
    function Get-SCSMObjectPrefix
    {
        Param ([string]$ClassName =$(throw "Please provide a classname"))
 
        switch ($ClassName)
        {
            default
            {
                #Get prefix from Activity Settings
                if ($ClassName.StartsWith("System.WorkItem.Activity") -or $ClassName.Equals("Microsoft.SystemCenter.Orchestrator.RunbookAutomationActivity"))
                {
                    $ActivitySettingsObj = Get-SCSMObject -Class (Get-SCSMClass -Id "5e04a50d-01d1-6fce-7946-15580aa8681d") -ComputerName $Server 
 
                    if ($ClassName.Equals("System.WorkItem.Activity.ReviewActivity")) {$prefix = $ActivitySettingsObj.SystemWorkItemActivityReviewActivityIdPrefix}
                    if ($ClassName.Equals("System.WorkItem.Activity.ManualActivity")) {$prefix = $ActivitySettingsObj.SystemWorkItemActivityManualActivityIdPrefix}
                    if ($ClassName.Equals("System.WorkItem.Activity.ParallelActivity")) {$prefix = $ActivitySettingsObj.SystemWorkItemActivityParallelActivityIdPrefix}
                    if ($ClassName.Equals("System.WorkItem.Activity.SequentialActivity")) {$prefix = $ActivitySettingsObj.SystemWorkItemActivitySequentialActivityIdPrefix}
                    if ($ClassName.Equals("System.WorkItem.Activity.DependentActivity")) {$prefix = $ActivitySettingsObj.SystemWorkItemActivityDependentActivityIdPrefix}
                    if ($ClassName.Equals("Microsoft.SystemCenter.Orchestrator.RunbookAutomationActivity")) {$prefix = $ActivitySettingsObj.MicrosoftSystemCenterOrchestratorRunbookAutomationActivityBaseIdPrefix}
                }
                else {throw "Class Name $ClassName is not supported"}
            }
        }
        return $prefix
    }

    #Function to set id prefix to activities in template
    function Update-SCSMPropertyCollection
    {
        Param (
            [Microsoft.EnterpriseManagement.Configuration.ManagementPackObjectTemplateObject]$Object =$(throw "Please provide a valid template object"),
            [bool]$ParentCollectionObject = $( throw "Please provide TRUE or FALSE for parent or child object" )
        )

        #DEBUG START
        if($debug -eq 1){ Write-Host -ForegroundColor Yellow "UpdateSCSMPropertyCollection" }
        #DEBUG END

        #Regex - Find class from template object property between ! and ']
        $pattern = '(?<=!)[^!]+?(?=''\])'
        if (($Object.Path) -match $pattern -and ($Matches[0].StartsWith("System.WorkItem.Activity") -or $Matches[0].StartsWith("Microsoft.SystemCenter.Orchestrator")))
        {
            #Set prefix from activity class
            $prefix = Get-SCSMObjectPrefix -ClassName $Matches[0]
 
            #Create template property object
            $propClass = [Microsoft.EnterpriseManagement.Configuration.ManagementPackObjectTemplateProperty]
            $propIdObject = New-Object $propClass
 
            #Add new item to property object
            $propIdObject.Path = "`$Context/Property[Type='$wi_alias!System.WorkItem']/Id$"
            $propIdObject.MixedValue = "$prefix{0}"
 
            #Add property id to template
            $Object.PropertyCollection.Add($propIdObject)
            
			#Works through the child activities in the template and updates their sequence number to fall in line with the current object
            If($ParentCollectionObject)
            {
                #Clear old values for recursive runs
                $oldProp = $null
                $newProp = $null

                #Look for the sequence ID property
                foreach($prop in $Object.PropertyCollection)
                {
                    #Update the sequence id property when found
                    if($prop.Path -match 'SequenceId')
                    {
                        #DEBUG START
                        if($debug -eq 1){ Write-Host -ForegroundColor Yellow "FoundSequenceProperty: "$prop.MixedValue }
                        #DEBUG END

                        #Get the existing sequence value
                        [int]$oldSeq = $prop.MixedValue

                        #Set the new property information
                        $prop.MixedValue = ($oldSeq + $SequenceStart)
                        
                        #DEBUG START
                        if($debug -eq 1){ Write-Host -ForegroundColor Yellow "NewSequenceProperty: "$prop.MixedValue }
                        #DEBUG END
                    }
                }
            }
 
            #recursively update activities in activities
            if ($Object.ObjectCollection.Count -ne 0)
            {
                foreach ($obj in $Object.ObjectCollection)
                { 
                    Update-SCSMPropertyCollection -Object $obj -ParentCollectionObject $false
                }       
            }
        }
    }

    #Function to apply template after it has been updated
    function Apply-SCSMTemplate
    {
        Param (
			[Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectProjection]$Projection =$(throw "Please provide a valid projection object"),
			[Microsoft.EnterpriseManagement.Configuration.ManagementPackObjectTemplate]$Template = $(throw 'Please provide an template object, ex. -template template')
		)
 
        #Get alias from system.workitem.library managementpack to set id property
        $templateMP = $Template.GetManagementPack()
        $wi_alias = $templateMP.References.GetAlias((Get-SCSMManagementPack system.workitem.library -ComputerName $Server))
 
        #Update Activities in template
        foreach ($TemplateObject in $Template.ObjectCollection)
        {
            Update-SCSMPropertyCollection -Object $TemplateObject -ParentCollectionObject $true
        }
        #Apply update template
        Set-SCSMObjectTemplate -Projection $Projection -Template $Template -ErrorAction Stop -ComputerName $Server 

        #DEBUG START
        if($debug -eq 1){ Write-Host "Successfully applied template: "$template.DisplayName " To: "$Projection.Object }
        #DEBUG END
    }
 
    #INITIALIZE
    #Get object from guid
    $emo = get-scsmobject -id $id -ComputerName $Server 
 
    #determine projection according to workitem type
    switch ($emo.GetLeastDerivedNonAbstractClass().Name)
    {
        "System.workitem.Incident" {$projName = "System.WorkItem.Incident.ProjectionType" }
        "System.workitem.ServiceRequest" {$projName = "System.WorkItem.ServiceRequestProjection"}
        "System.workitem.ChangeRequest" {$projName = "System.WorkItem.ChangeRequestProjection"}
        "System.workitem.Problem" {$projName = "System.WorkItem.Problem.ProjectionType"}
        "System.workitem.ReleaseRecord" {$projName = "System.WorkItem.ReleaseRecordProjection"}
        "System.workitem.Activity.SequentialActivity" {$projName = "System.WorkItem.Activity.SequentialActivityProjection"}
        "System.workitem.Activity.ParallelActivity" {$projName = "System.WorkItem.Activity.ParallelActivityProjection"}
 
        default {throw "$emo is not a supported workitem type"}
    }
    
    #Get object projection
    $emoID = $emo.id
    $WIproj = Get-SCSMObjectProjection -ProjectionName $projName -Filter "Id -eq $emoID" -ComputerName $Server 
    
    #Get template from displayname or id
    if ($TemplateDisplayName)
    {
        $template = Get-SCSMObjectTemplate -DisplayName $TemplateDisplayName -ComputerName $Server 
    }
    elseif ($templateId)
    {
        $template = Get-SCSMObjectTemplate -id $TemplateId -ComputerName $Server 
    }
    else
    {
        throw "Please provide either a template id or a template displayname to apply"
    }
    
    #Execute apply-template function if id and 1 template exists
    if (@($template).count -eq 1)
    {
        if ($WIProj)
        {
            Apply-SCSMTemplate -Projection $WIproj -Template $template
        }
        else {throw "Id $Id cannot be found";}
    }
    else {throw "Template cannot be found or there was more than one result"}

}

#Activity Class Names
$raClassName = "System.WorkItem.Activity.ReviewActivity"
$maClassName = "System.WorkItem.Activity.ManualActivity"
$paClassName = "System.WorkItem.Activity.ParallelActivity"
$saClassName = "System.WorkItem.Activity.SequentialActivity"
$rbClassName = "Microsoft.SystemCenter.Orchestrator.RunbookAutomationActivity"

#Class Objects
$relContainsActGUID = "2da498be-0485-b2b2-d520-6ebd1698e61b"
$relContainsActClass = Get-SCSMRelationshipClass -Id $relContainsActGUID -ComputerName $Server 
$rootObj = Get-SCSMObject -Id $srGUID -ComputerName $Server
 
#Enumerations
$enumYes = "Enum.a8231d9d149b445687c4eddf51c9549d"
$enumNo = "Enum.8fccb6a092eb4b57a89aa45489851a68"
$enumUndecided = "Enum.0ba90330d884495084cae7b3888ae365"
$enumCompleted = "ActivityStatusEnum.Completed"
$enumInProgress = "ActivityStatusEnum.Active"
$enumPending = "ActivityStatusEnum.Ready"

#Main Body
#START DEBUG
if($debug -eq 1){ Write-Host -ForegroundColor Green "I'm starting up!" }
#END DEBUG

WalkActivityTree($rootObj)